在 React 中,當父組件重新渲染時,內部的變數和函式也會被重新創建,這可能導致子組件不必要的重新渲染,進而影響效能。為了避免這種情況,我們可以使用 memo
來包裹子組件,讓 React 記憶子組件的 props
,當 props
改變時才重新渲染子組件。
但函式每次重新創建時,記憶 props
的 memo
並不起作用。因此需要 useCallback
來記憶函式,避免每次父組件渲染時都被重新創建。
useCallback
會返回一個記住的函式,只有在其依賴項改變時才會重新創建這個函式,從而避免不必要的渲染。
const memoizedCallback = useCallback(() => {
// 函式邏輯
}, [依賴項]);
useCallback
的適用場景:props
傳遞給子組件:當父組件狀態或 props
改變時,父組件重新渲染會導致函式重新創建,進而引發不必要的子組件渲染。useCallback
可以避免這種情況。useCallback
,並將這些依賴變數傳入依賴陣列。useCallback
import { useState, memo } from 'react'
const App = () => {
const [count, setCount] = useState(0)
const [todos, setTodos] = useState([])
const increment = () => {
setCount(c => c + 1)
}
// 沒有使用 useCallback 的 addTodo 函式
const addTodo = () => {
setTodos(t => [...t, 'New Todo'])
}
return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
)
}
// 使用 memo 優化 Todos 組件,避免不必要的重新渲染
const Todos = memo(({ todos, addTodo }) => {
console.log('child render')
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
<button onClick={addTodo}>Add Todo</button>
</>
)
})
export default App
當你觀察上面的範例後,父組件重新渲染後,子組件也是有重新渲染的!
主要的原因是:
App
組件重新渲染,addTodo
函式都會重新創建一個新的記憶體參考。Todos
組件的 props
會因為addTodo
有變化而觸發 memo
的重新渲染,但實際上 todos
沒有發生變化。useCallback
解決問題我們可以通過 useCallback
來記憶 addTodo
函式,這樣每次 App
重新渲染時,addTodo
的記憶體參考就不會變化,便能避免不必要的渲染。
import { useState, memo, useCallback } from 'react'
const App = () => {
const [count, setCount] = useState(0)
const [todos, setTodos] = useState([])
const increment = () => {
setCount(c => c + 1)
}
// 使用 useCallback 記憶 addTodo 函式
const addTodo = useCallback(() => {
setTodos(t => [...t, 'New Todo'])
}, [todos])
return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
)
}
const Todos = memo(({ todos, addTodo }) => {
console.log('child render')
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
<button onClick={addTodo}>Add Todo</button>
</>
)
})
export default App
useCallback
:useCallback
會記憶住 addTodo
函式,只有在 todos
改變時才會重新創建這個函式。todos
沒有變化,addTodo
的記憶體參考就不會改變,Todos
組件也不會重新渲染。memo
和 useCallback
組合後,可以有效避免子組件的無效渲染,提升效能。當子組件的props有函式傳遞時,若有想要用 memo
優化子組件時,也記得要搭配上useCallback
,能減少子組件不必要的重新渲染。
本文將會同步更新到我的部落格